/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 * 
 * The contents of this file are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License specifically applicable to 
 * this software at http://developer.yahoo.com/yslow/license.txt, or the general 
 * form at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 * 
 * The Original Code is: YSlow
 * 
 * The Initial Developer of the Original Code is Yahoo! Inc.
 * 
 * Copyright (C) 2007, Yahoo! Inc. All Rights Reserved.
 * 
 * Contributor(s): 
 * ***** END LICENSE BLOCK ***** */

////////////////////////////////////////////////////////////////////////////////
// JSLint
////////////////////////////////////////////////////////////////////////////////

//Create the Lints namespace within the global YSLOW namespace.
if ( typeof YSLOW.JSLint == "undefined") {
	YSLOW.JSLint = {};
}

YSLOW.JSLint.loadJSLint = function() {
	FirebugContext.yslowContext['jslintcntr'] = 0;
	YSLOW.JSLint.launchJSLint();	
}


// Complications:
// - We need to submit ALL the JS code in one call to JSLINT. Otherwise, JSLINT
//   complains about undefined symbols that are from other external js files.
YSLOW.JSLint.launchJSLint = function() {
	if ( "function" != typeof(JSLINT) ) {
		FirebugContext.yslowContext['jslintcntr']++;
	    if ( 5 < FirebugContext.yslowContext['jslintcntr'] ) {
			alert("Failed to load fulljslint.js! continuing anyway");
		}
		else {
			setTimeout("YSLOW.JSLint.launchJSLint()", 1000);
    		return;
		}
	}

	// If we got here, the jslint file is loaded.
	var aJSCode = new Array();
	var sJSCode = "";
	var aScriptsInfo = new Array();
	var nLineTotal = 0;

	// Iterate over the external JS files.
	var aComponents = FirebugContext.yslowContext.getComponents("js");
	for ( var i = 0; i < aComponents.length; i++ ) {
		var compObj = aComponents[i];
		if ( compObj['responseText'] ) {
			sJSCode += compObj['responseText'];
			aJSCode = aJSCode.concat(compObj['responseText'].split("\n"));
			aScriptsInfo.push( { url: compObj['url'], lines: (aJSCode.length-nLineTotal) } );
			nLineTotal = aJSCode.length;
		}
	}

	// Iterate over the inline SCRIPT blocks.
	var aScripts = FirebugContext.yslowContext["document"].getElementsByTagName("script");
	var bFirstInline = true;
	var iScripts = 0;
	for ( var i = 0; i < aScripts.length; i++ ) {
		var script = aScripts[i];
		if ( !script.src ) {  // avoid external script objects
			iScripts++;
			sJSCode += script.innerHTML;
			aJSCode = aJSCode.concat(script.innerHTML.split("\n"));
			aScriptsInfo.push( { url: "script tag #" + iScripts, lines: (aJSCode.length-nLineTotal) } );
			nLineTotal = aJSCode.length;
		}
	}

	var lastErrObj;  // So we can track if JSLint bailed early.
	var sErrors = "";
	if ( ! JSLINT(aJSCode, { browser : true, undef : true }) ) {
		FirebugContext.yslowContext["jslintUrl"] = "";   // keep track of the current script URL we're evaluating
		var hClasses = {};
		var nClasses = 0;
		for ( var i = 0; i < JSLINT.errors.length; i++ ) {
			var errObj = JSLINT.errors[i];
			if ( errObj ) {         // the last errObj can be NULL
				lastErrObj = errObj;
				sReason = YSLOW.JSLint.cleanReason(errObj['reason']);
				if ( ! hClasses[ sReason ] ) {
					// Give each error type a unique #. We use this to generate a css classname for hiding errors by type.
					nClasses++;
					hClasses[ sReason ] = nClasses;
				}
			    sErrors += YSLOW.JSLint.genError(errObj, aScriptsInfo, aJSCode, "errclass" + hClasses[sReason], i);
			}
		}
		sErrors += "</div> <!-- last div?? -->";  // close the last div
	}

	var sErrCount = "<div class=bailed>" + ( 0 == JSLINT.errors.length || JSLINT.errors[JSLINT.errors.length-1] ? JSLINT.errors.length : JSLINT.errors.length-1 ) + " problems found.</div>\n";
	var sBailed = "";
	if ( lastErrObj && lastErrObj.reason.match(/Too many errors. \(([0-9]*)% scanned/) ) {
		// JSLint bailed early because of too many errors. Make it clear to the user.
		sBailed = "<div class=bailed>JSLint stopped after analyzing " + RegExp.$1 + "% of the code because there were too many errors.</div>\n";
	}

	FirebugContext.yslowContext["jslintwindow"] = window.open("", "_blank");
	if ( ! FirebugContext.yslowContext["jslintwindow"] ) {
		alert("ERROR: Problem opening JS Inspector window.");
		return;
	}

	sHtml = 
'<html>\n' +
'<head>\n' +
'<title>JSLint Report for: ' + FirebugContext.yslowContext["document"].location.href.substring(0, 25) + '</title>' +
'<link rel="stylesheet" type="text/css" href="chrome://yslow/content/yslow.css">\n' +
'<scr' + 'ipt>\n' +
'YSLOW_NO = true;\n' +
'function ignoreError(sClass) {\n' +
'	var aDivs = document.getElementsByTagName("div");\n' +
'	for ( var i = 0; i < aDivs.length; i++ ) {\n' +
'		if ( -1 != aDivs[i].className.indexOf(sClass) ) {\n' +
'			aDivs[i].style.display = "none";\n' +
'		}\n' +
'	}\n' +
'}\n' +
'function exp_col(id, bExpand) {\n' +
'	var element = document.getElementById(id);\n' +
'	var image = document.getElementById(id + "_img");\n' +
'	if ( element && image ) {\n' +
'		if ( "undefined" == typeof(bExpand) ) {\n' +
'			bExpand = ( -1 == image.src.indexOf("open") );\n' +
'		}\n' +
'		element.style.display = ( bExpand ? "block" : "none" );\n' +
'		image.src = "http://us.i1.yimg.com/us.yimg.com/i/us/plus/csp/cn/norgie_" + ( bExpand ? "open" : "closed" ) + "_dna.gif";\n' +
'	}\n' +
'}\n' +
'</scr' + 'ipt>\n' +
'</head>\n' +
'' +
'<body style="font-family: Lucida Grande, Tahoma, sans-serif; font-size: 10pt;">\n' +
'<div class=titleheader><font style="font-size: 2em;">JSLint Report for: </font>' + FirebugContext.yslowContext["document"].location + '</div>\n' +
sErrCount + sBailed +
'<p style="margin-top: 8px;">\n' + sErrors +
'<p style="margin-top: 15px;"><hr><a href="http://jslint.com/">JSLint</a> is courtesy of <a href="mailto:douglas@crockford.com">Douglas Crockford</a>. Copyright 2002. <a href="http://www.crockford.com/">All Rights Reserved Wrrrldwide</a>.' +
'</body>\n' +
'</html>\n' +
'';

	FirebugContext.yslowContext["jslintwindow"].document.open();
	FirebugContext.yslowContext["jslintwindow"].document.writeln(sHtml);
	FirebugContext.yslowContext["jslintwindow"].document.close();
}


// Extract a substring from the line of JS code that includes the error location.
// Also close/open new div when the JS source (external file or inline) changes.
YSLOW.JSLint.genError = function(errObj, aScriptsInfo, aJSCode, sClassname, iError) {
	var nLineTotal = 0;
	var iRelLine = 0;
	var sScript = "";
	var sResults = "";
	var iLine = parseInt(errObj['line']);   // this is relative to all the js combined together and is ZERO BASED!!
	var iChar = parseInt(errObj['character']);   // this is relative to all the js combined together
	for ( var i = 0; i < aScriptsInfo.length; i++ ) {
		if ( iLine < (nLineTotal + aScriptsInfo[i]['lines']) ) {
			iRelLine = iLine - nLineTotal + 1;  // because it's zero-based
			sScript = aScriptsInfo[i]['url'];
			break;
		}
		nLineTotal += aScriptsInfo[i]['lines'];
    }

	if ( sScript != FirebugContext.yslowContext["jslintUrl"] ) {
		sResults += ( FirebugContext.yslowContext["jslintUrl"] ? "</div></div>" : "" );   // close the previous div (if there was one)
		var exp_col_id = "jslintdiv_" + iError;    // iError is unique across all divs
		var hrefScript = 'target="_blank" href="' + sScript + '"';;
		if ( -1 != sScript.indexOf("script tag") ) {
			sResults += '<div class=scriptheader>' +
						'<a href="javascript:exp_col(\'' + exp_col_id + '\')"><img id="' + exp_col_id + '_img" border=0 src="http://us.i1.yimg.com/us.yimg.com/i/us/plus/csp/cn/norgie_open_dna.gif"></a> ' +
						'<b>' + sScript + '</b></div><div class=exp_col_div id=' + exp_col_id + '>';
		}
		else {
			sResults += '<div class=scriptheader>' +
						'<a href="javascript:exp_col(\'' + exp_col_id + '\')"><img id="' + exp_col_id + '_img" border=0 src="http://us.i1.yimg.com/us.yimg.com/i/us/plus/csp/cn/norgie_open_dna.gif"></a> ' +
						'<a ' + hrefScript + '><b>' + sScript + '</b></a></div><div class=exp_col_div id=' + exp_col_id + '>';
		}
		FirebugContext.yslowContext["jslintUrl"] = sScript;
	}

	sResults += "<div class='" + sClassname + "," + ( 0 == YSLOW.Utils.mod(iError, 2) ? "jserrorEven" : "jserrorOdd" ) + "'>";
	sResults += YSLOW.Utils.escapeHtml(errObj['reason']) + " line " + iRelLine + ", char " + iChar + ":&nbsp;&nbsp;&nbsp;" +
				"<a href='javascript:ignoreError(\"" + sClassname + "\")' class=eplink style='font-size: 0.8em;'>ignore all</a>";
	var sLine = aJSCode[iLine];
	var iDelta = 20;
	var i1 = ( iChar - iDelta >= 0 ? iChar - iDelta : 0 );
	var i2 = iChar + iDelta;
	// Find a better breaking point - semi-colon?
	if ( i1 < 30 ) {
		i1 = 0;  // start at the beginning if we're already close to there
	}
	if ( 0 != i1 && -1 != sLine.lastIndexOf(';', i1) ) {
		i1 = sLine.lastIndexOf(';', i1) + 1;
	}
	if ( i2 > sLine.length-30 ) {
		i2 = sLine.length-1;
	}
	if ( i2 < sLine.length-1 && -1 != sLine.indexOf(';', i2) ) {
		i2 = sLine.indexOf(';', i2) + 1;
	}
	if ( iChar >= i2 ) {
		i2 = iChar+1;
	}

	sResults += "<div style='margin-left: 30px;'><nobr><code>" + ( 0 == i1 ? "" : "..." ) +
				YSLOW.Utils.escapeHtml(sLine.substring( i1, iChar )) + 
				"<span style='background: #FBB'>" + sLine.substring( iChar, iChar+1 ) + "</span>" + 
				YSLOW.Utils.escapeHtml(sLine.substring(iChar+1, i2)) + 
				( i2 >= sLine.length-1 ? "" : "..." ) + "</code>" + "</nobr></div></div>\n";

	return sResults;
}


// Some reasons are different, but are really the same.
// Normalize them here.
YSLOW.JSLint.cleanReason = function(sReason) {
	if ( -1 != sReason.indexOf("Missing '{' before") ) {
		sReason = "Missing '{' before";
	}

	return sReason;
}


////////////////////////////////////////////////////////////////////////////////
// END JSLint
////////////////////////////////////////////////////////////////////////////////
